package com.jcodecraeer.xrecyclerview;

import ohos.agp.components.*;
import ohos.agp.database.DataSetSubscriber;
import ohos.app.Context;
import ohos.eventhandler.EventHandler;
import ohos.eventhandler.EventRunner;
import ohos.multimodalinput.event.TouchEvent;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;

import static ohos.agp.utils.LayoutAlignment.TOP;

public class XRecyclerView extends ListContainer  implements Component.LayoutRefreshedListener,
        Component.TouchEventListener{
    //下面的ItemViewType是保留值(ReservedItemViewType),如果用户的adapter与它们重复将会强制抛出异常。不过为了简化,我们检测到重复时对用户的提示是ItemViewType必须小于10000
    private static final int TYPE_REFRESH_HEADER = 10000;//设置一个很大的数字,尽可能避免和用户的adapter冲突
    private static final int TYPE_FOOTER = 10001;
    private static final int HEADER_INIT_INDEX = 10002;
    private static List<Integer> sHeaderTypes = new ArrayList<>();//每个header必须有不同的type,不然滚动的时候顺序会变化
    private static ArrayList<Component> mHeaderViews = new ArrayList<>();

    private int mRefreshProgressStyle = ProgressStyle.SysProgress;
    private int mLoadingMoreProgressStyle = ProgressStyle.SysProgress;
    // limit number to call load more
    // 控制多出多少条的时候调用 onLoadMore
    private int limitNumberToCallLoadMore = 1;
    private static int height;
    private float mLastY = -1;
    private float dragRate = 3;

    private BaseItemProvider mWrapAdapter;
    private static ArrowRefreshHeader mRefreshHeader;
    private static LoadingMoreFooter footView;
    private LoadingListener mLoadingListener;
    private ScrollHelper mScrollhelper;
    private DataSetSubscriber mDataObserver = new DataObserver();

    //adapter没有数据的时候显示,类似于listView的emptyView
    private Component mEmptyView;
    private Component mFootView;
    private static Component allComponent;
    private CustomFooterViewCallBack footerViewCallBack;

    //是否下拉刷新
    private boolean pullRefreshEnabled = true;
    private boolean refreshEnabled = false;
    //上拉刷新加载更多数据
    private static boolean loadingMoreEnabled = true;
    private static boolean isLoadingData = false;
    private boolean isNoMore = false;

    public XRecyclerView(Context context) {
        super(context);
        init(context);
    }

    public XRecyclerView(Context context, AttrSet attrSet) {
        super(context,attrSet);
        init(context);
    }

    public XRecyclerView(Context context, AttrSet attrSet, String styleName) {
        super(context, attrSet, styleName);
        init(context);
    }

    private void init(Context context){
        mHeaderViews.clear();
        mScrollhelper = new ScrollHelper();
        setTouchEventListener(this);
        setLayoutRefreshedListener(this);
        if(pullRefreshEnabled){
            mRefreshHeader = new ArrowRefreshHeader(context);
            mRefreshHeader.setProgressStyle(mRefreshProgressStyle);
        }

        footView = new LoadingMoreFooter(context);
        footView.setProgressStyle(mLoadingMoreProgressStyle);
        mFootView = footView;
        mFootView.setVisibility(HIDE);
    }

    /**
     * call it when you finish the activity,
     * when you call this,better don't call some kind of functions like
     * RefreshHeader,because the reference of mHeaderViews is NULL.
     */
    public void destroy(){
        if(mHeaderViews != null){
            mHeaderViews.clear();
            mHeaderViews = null;
        }
        if(mFootView instanceof LoadingMoreFooter){
            ((LoadingMoreFooter) mFootView).destroy();
            mFootView = null;
        }
        if(mRefreshHeader != null){
            mRefreshHeader.destroy();
            mRefreshHeader = null;
        }
    }

    public ArrowRefreshHeader getDefaultRefreshHeaderView(){
        if(mRefreshHeader == null){
            return null;
        }
        return mRefreshHeader;
    }

    public LoadingMoreFooter getDefaultFootView(){
        if(mFootView == null){
            return null;
        }
        if(mFootView instanceof LoadingMoreFooter){
            return ((LoadingMoreFooter) mFootView);
        }
        return null;
    }

    // set the number to control call load more,see the demo on linearActivity
    public void setLimitNumberToCallLoadMore(int limitNumberToCallLoadMore) {
        this.limitNumberToCallLoadMore = limitNumberToCallLoadMore;
    }

    public Component getFootView(){
        return mFootView;
    }

    public void setFootViewText(String loading, String noMore) {
        if(mFootView instanceof LoadingMoreFooter){
            ((LoadingMoreFooter) mFootView).setLoadingHint(loading);
            ((LoadingMoreFooter) mFootView).setNoMoreHint(noMore);
        }
    }

    @SuppressWarnings("all")
    public void setFootView(final Component view,CustomFooterViewCallBack footerViewCallBack) {
        if(view == null || footerViewCallBack == null){
            return;
        }
        mFootView = view;
        this.footerViewCallBack = footerViewCallBack;
    }
    // Fix issues (多个地方使用该控件的时候，所有刷新时间都相同 #359)
    public void setRefreshTimeSpKeyName(String keyName){
        if(mRefreshHeader != null){
            mRefreshHeader.setXrRefreshTimeKey(keyName);
        }
    }

    public void loadMoreComplete() {
        isLoadingData = false;
        if (mFootView instanceof LoadingMoreFooter ) {
            ((LoadingMoreFooter) mFootView).setState(LoadingMoreFooter.STATE_COMPLETE);
        } else {
            if(footerViewCallBack != null){
                footerViewCallBack.onLoadMoreComplete(mFootView);
            }
        }
    }

    public void setNoMore(boolean noMore){
        isLoadingData = false;
        isNoMore = noMore;
        if (mFootView instanceof LoadingMoreFooter) {
            ((LoadingMoreFooter) mFootView).setState(isNoMore ? LoadingMoreFooter.STATE_NOMORE:LoadingMoreFooter.STATE_COMPLETE);
        } else {
            if(footerViewCallBack != null){
                footerViewCallBack.onSetNoMore(mFootView,noMore);
            }
        }
    }

    public void refresh() {
        if (pullRefreshEnabled && mLoadingListener != null) {
            mRefreshHeader.setState(ArrowRefreshHeader.STATE_REFRESHING);
            mLoadingListener.onRefresh();
        }
    }

    public void reset(){
        setNoMore(false);
        loadMoreComplete();
        refreshComplete();
    }

    public void refreshComplete() {
        if(mRefreshHeader != null)
            mRefreshHeader.refreshComplete();
        if(mWrapAdapter != null) mWrapAdapter.notifyDataChanged();
        setNoMore(false);
    }

    public void setRefreshHeader(ArrowRefreshHeader refreshHeader) {
        mRefreshHeader = refreshHeader;
    }

    public void setPullRefreshEnabled(boolean enabled) {
        pullRefreshEnabled = enabled;
    }

    public void setLoadingMoreEnabled(boolean enabled) {
        loadingMoreEnabled = enabled;
        if (!enabled) {
            if (mFootView instanceof LoadingMoreFooter) {
                ((LoadingMoreFooter)mFootView).setState(LoadingMoreFooter.STATE_COMPLETE);
            }
        }
    }

    public void setRefreshProgressStyle(int style) {
        mRefreshProgressStyle = style;
        if (mRefreshHeader != null) {
            mRefreshHeader.setProgressStyle(style);
        }
    }

    public void setRefreshProgressIndicatorColor(int color) {
        if (mRefreshHeader != null) {
            mRefreshHeader.setIndicatorColor(color);
        }
    }

    public void setLoadingMoreProgressStyle(int style) {
        mLoadingMoreProgressStyle = style;
        if (mFootView instanceof LoadingMoreFooter) {
            ((LoadingMoreFooter) mFootView).setProgressStyle(style);
        }
    }
    public void setLoadingMoreProgressIndicatorColor(int color) {
        if (mFootView instanceof LoadingMoreFooter) {
            ((LoadingMoreFooter) mFootView).setIndicatorColor(color);
        }
    }

    //设置下拉时图图标样式
    public void setArrowImageView(int resId) {
        if (mRefreshHeader != null) {
            mRefreshHeader.setArrowImageView(resId);
        }
    }

    // 设置下拉时候的偏移计量因子。y = deltaY/dragRate
    // dragRate 越大，意味着，用户要下拉滑动更久来触发下拉刷新。相反越小，就越短距离
    public void setDragRate(float rate){
        if(rate <= 0.5){
            return;
        }
        dragRate = rate;
    }

    // if you can't sure that you are 100% going to
    // have no data load back from server anymore,do not use this
    @Deprecated
    public void setEmptyView(Component emptyView) {
        this.mEmptyView = emptyView;
        mDataObserver.onChanged();
    }

    @Deprecated
    public Component getEmptyView() {
        return mEmptyView;
    }

    //上拉加载更多数据
    private void initComputeScroll() {
        setScrolledListener(new ScrolledListener() {
            @Override
            public void onContentScrolled(Component component, int scrollX, int scrollY, int oldScrollX, int  oldScrollY) {
                if(mScrollhelper.isFinished()){
                    int lastIndex = getLastVisibleItemPosition();
                    int adjLen = (loadingMoreEnabled?2:1);
                    if(mLoadingListener != null && !isLoadingData && loadingMoreEnabled && lastIndex == mWrapAdapter.getCount()-adjLen){
                        if(footView.getVisibility() == HIDE){
                            isLoadingData = true;
                            if (mFootView instanceof LoadingMoreFooter) {
                                ((LoadingMoreFooter) mFootView).setState(LoadingMoreFooter.STATE_LOADING);
                            } else {
                                if(footerViewCallBack != null){
                                    footerViewCallBack.onLoadingMore(mFootView);
                                }
                            }
                        }

                        if(footView.getVisibility() == VISIBLE ){
                            mLoadingListener.onLoadMore();
                        }
                    }
                }
            }
        });
    }

    public void setLoadingListener(LoadingListener listener) {
        mLoadingListener = listener;
    }

    public interface LoadingListener {

        void onRefresh();

        void onLoadMore();
    }

    @Override
    public void setItemProvider(BaseItemProvider itemProvider) {
        mWrapAdapter = itemProvider;
        super.setItemProvider(itemProvider);
    }

    @Override
    public BaseItemProvider getItemProvider() {
        return super.getItemProvider();
    }

    @Override
    public void onRefreshed(Component component) {
        initComputeScroll();
        invalidate();
    }

    @Override
    public boolean onTouchEvent(Component component, TouchEvent ev) {
        int firstIndex = getItemPosByVisibleIndex(0);
        switch (ev.getAction()){
            case TouchEvent.PRIMARY_POINT_DOWN:
                mLastY = ev.getPointerScreenPosition(0).getY();
                break;
            case TouchEvent.POINT_MOVE:
                if(mRefreshHeader != null && mRefreshHeader.getState() == ArrowRefreshHeader.STATE_REFRESHING && mRefreshHeader.getVisibleHeight() != 0) {
                    return false;
                }
                float deltaY = ev.getPointerScreenPosition(0).getY() -mLastY;
                if( pullRefreshEnabled && firstIndex <= 1){
                    refreshEnabled = true;
                    if(mRefreshHeader == null){
                        break;
                    }
                    mRefreshHeader.onMove(deltaY / dragRate);
                    mWrapAdapter.notifyDataSetItemRangeChanged(1,2);
                    invalidate();
                    if(mRefreshHeader.getVisibleHeight() > 0 && mRefreshHeader.getState() < ArrowRefreshHeader.STATE_REFRESHING){
                        return true;
                    }
                } else {
                    mLastY = ev.getPointerScreenPosition(0).getY();
                }
                break;
            case TouchEvent.CANCEL:
            case TouchEvent.PRIMARY_POINT_UP:
                mLastY = -1;
                if(pullRefreshEnabled && refreshEnabled){
                    refreshEnabled = false;
                    if(mRefreshHeader != null && mRefreshHeader.releaseAction() && firstIndex == 0){
                        if(mLoadingListener != null){
                            mLoadingListener.onRefresh();
                            mWrapAdapter.notifyDataChanged();
                        }
                    } else {
                        mWrapAdapter.notifyDataChanged();
                    }
                }
                break;

        }
        return true;
    }

    public void addHeaderView(Component view) {
        if(mHeaderViews == null || sHeaderTypes == null)
            return;
        sHeaderTypes.add(HEADER_INIT_INDEX + mHeaderViews.size());
        mHeaderViews.add(view);
        if (mWrapAdapter != null) {
            mWrapAdapter.notifyDataChanged();
        }
    }

    public void removeHeaderView( Component v){
        if(mHeaderViews == null || sHeaderTypes == null || v == null)
            return;
        for (Component view : mHeaderViews) {
            if (view == v) {
                mHeaderViews.remove(view);
                break;
            }
        }
        if (mWrapAdapter != null) {
            mWrapAdapter.notifyDataChanged();
        }
    }

    public void removeAllHeaderView(){
        if(mHeaderViews == null || sHeaderTypes == null)
            return;
        mHeaderViews.clear();
        if (mWrapAdapter != null) {
            mWrapAdapter.notifyDataChanged();
        }
    }

    /** =======================================================  ItemProvider start======================================================= */
    public static abstract class RecyclerAdapter<T> extends BaseItemProvider {
        private Context context;
        private List<T> data;
        private int mLayoutId;
        private int numColumns = 1;
        private OnItemClickListener onItemClickListener;

        public RecyclerAdapter(Context context, List<T> data, int mLayoutId) {
            this.context = context;
            this.data = data;
            this.mLayoutId = mLayoutId;
        }

        public List<T> getData() {
            return data;
        }

        @Override
        public int getCount() {
            int adjLen = (loadingMoreEnabled?2:1)+mHeaderViews.size();
            return (data.size() % numColumns == 0 ? data.size() / numColumns : data.size() / numColumns + 1)+adjLen;
        }

        @Override
        public T getItem(int position) {
            return data.get(position);
        }

        @Override
        public long getItemId(int position) {
            return position;
        }

        @Override
        public int getItemComponentType(int position) {
            if(position == getCount() -1){
                return TYPE_FOOTER;
            }
            return super.getItemComponentType(position);
        }

        @Override
        public Component getComponent(int position, Component convertComponent, ComponentContainer parent) {
            if(position == 0 ){
                return mRefreshHeader;
            } else if(position > 0 && position < mHeaderViews.size() + 1){
                Component component = mHeaderViews.get(position-1);
                return component;
            } else if( position == getCount()-1){
                return footView;
            }

            ViewHolder viewHolder;
            convertComponent = new DirectionalLayout(context);
            ((DirectionalLayout) convertComponent).setOrientation(Component.HORIZONTAL);
            ComponentContainer.LayoutConfig layoutConfig = convertComponent.getLayoutConfig();
            layoutConfig.width = DependentLayout.LayoutConfig.MATCH_PARENT;
            convertComponent.setLayoutConfig(layoutConfig);

            for (int i = 0; i < numColumns; i++) {
                if ((position -1 ) * numColumns + i < data.size()) {
                    DirectionalLayout dlItemParent = new DirectionalLayout(context);
                    dlItemParent.setLayoutConfig(new DirectionalLayout.LayoutConfig(0, DirectionalLayout.LayoutConfig.MATCH_CONTENT, TOP, 1));
                    height = dlItemParent.getHeight();
                    Component childConvertComponent = LayoutScatter.getInstance(context).parse(mLayoutId, null, false);
                    int finalI = i;
                    childConvertComponent.setClickedListener(new Component.ClickedListener() {
                        @Override
                        public void onClick(Component component) {
                            onItemClickListener.onItemClick(component, (position -1) * numColumns + finalI);
                        }
                    });
                    dlItemParent.addComponent(childConvertComponent);
                    ((ComponentContainer) convertComponent).addComponent(dlItemParent);
                    viewHolder = new ViewHolder(childConvertComponent);
                    bind(viewHolder, getItem((position -1) * numColumns + i), (position -1) * numColumns + i);
                }
                else {
                    Component childConvertComponent = new Component(context);
                    DirectionalLayout.LayoutConfig layoutConfig1 = new DirectionalLayout.LayoutConfig(0, height);
                    layoutConfig1.weight = 1;
                    childConvertComponent.setLayoutConfig(layoutConfig1);
                    ((ComponentContainer) convertComponent).addComponent(childConvertComponent);
                }
            }
            return convertComponent;
        }

        public void setNumColumns(int numColumns) {
            this.numColumns = numColumns;
        }

        public void setOnItemClickListener(OnItemClickListener onItemClickListener) {
            this.onItemClickListener = onItemClickListener;
        }

        public abstract void bind(ViewHolder holder, T s, int position);

        public  class ViewHolder  {
            HashMap<Integer, Component> mViews = new HashMap<>();
            public Component itemView;
            public ViewHolder(Component component) {
                this.itemView = component;
            }

            public <E extends Component> E getView(int viewId) {
                E view = (E) mViews.get(viewId);
                if (view == null) {
                    view = (E) itemView.findComponentById(viewId);
                    mViews.put(viewId, view);
                }
                return view;
            }
        }

        public  abstract static class OnItemClickListener {
            public abstract void onItemClick(Component component, int position);
        }

    }

    /** =======================================================  ItemProvider end======================================================= */
    private class DataObserver extends DataSetSubscriber{
        @Override
        public void onChanged() {
            if(mWrapAdapter != null){
                mWrapAdapter.notifyDataChanged();
            }
        }

        @Override
        public void onItemChanged(int position) {
            mWrapAdapter.notifyDataSetItemChanged(position);
        }

        @Override
        public void onItemInserted(int position) {
            mWrapAdapter.notifyDataSetItemInserted(position);
        }

        @Override
        public void onItemRemoved(int position) {
            mWrapAdapter.notifyDataSetItemRemoved(position);
        }

        @Override
        public void onItemRangeChanged(int startPos, int countItems) {
            mWrapAdapter.notifyDataSetItemRangeChanged(startPos,countItems);
        }

        @Override
        public void onItemRangeInserted(int startPos, int countItems) {
            mWrapAdapter.notifyDataSetItemRangeInserted(startPos,countItems);
        }

        @Override
        public void onItemRangeRemoved(int startPos, int countItems) {
            mWrapAdapter.notifyDataSetItemRangeRemoved(startPos,countItems);
        }
    }
}
